#include "struct_pack_sample.hpp"

#include <memory>
#include <stdexcept>
#include <unordered_map>
#include <vector>

#if __has_include(<msgpack.hpp>)
#    define HAVE_MSGPACK 1
#endif

#ifdef HAVE_MSGPACK
#    include "msgpack_sample.hpp"
#endif

#ifdef HAVE_BOOST_SERIALIZATION
#    include "boost_serialization_sample.hpp"
#endif

#ifdef HAVE_PROTOBUF
#    include "protobuf_sample.hpp"
#    ifdef HAVE_STRUCT_PB
#        include "struct_pb_sample.hpp"
#    endif
#endif
#ifdef HAVE_FLATBUFFER
#    include "flatbuffer_sample.hpp"
#endif

#include "config.hpp"
using namespace std::string_literals;
template <typename T>
void calculate_serialize_rate(const T &map,
                              LibType base_line_type,
                              SampleType sample_type) {
    auto it = map.find(base_line_type);
    if (it == map.end()) {
        throw std::invalid_argument("unknown lib type");
    }

    auto base_lib = it->second;
    auto base_line_ser
        = base_lib->get_serialize_time_elapsed_map()[sample_type];
    auto base_line_deser
        = base_lib->get_deserialize_time_elapsed_map()[sample_type];

    for (auto [lib_type, other_lib] : map) {
        if (lib_type == base_line_type) {
            continue;
        }

        auto ns_ser = other_lib->get_serialize_time_elapsed_map()[sample_type];
        auto ns_deser
            = other_lib->get_deserialize_time_elapsed_map()[sample_type];
        double rate_ser
            = std::ceil(double(ns_ser) / base_line_ser * 100.0) / 100.0;
        double rate_deser
            = std::ceil(double(ns_deser) / base_line_deser * 100.0) / 100.0;

        std::string prefix_ser = base_lib->name()
                                     .append(" serialize   ")
                                     .append(get_sample_name(sample_type))
                                     .append(" is ");
        std::string prefix_deser = base_lib->name()
                                       .append(" deserialize ")
                                       .append(get_sample_name(sample_type))
                                       .append(" is ");

        auto space_ser = get_space_str(prefix_ser.size(), 39);
        auto space_deser = get_space_str(prefix_deser.size(), 39);

        std::cout << prefix_ser << space_ser << "[" << rate_ser
                  << "] times faster than " << other_lib->name() << ", ["
                  << base_line_ser << ", " << ns_ser << "]"
                  << "\n";
        std::cout << prefix_deser << space_deser << "[" << rate_deser
                  << "] times faster than " << other_lib->name() << ", ["
                  << base_line_deser << ", " << ns_deser << "]"
                  << "\n";
    }

    std::cout << "========================\n";
}
template <typename T>
void run_benchmark(const T &map, LibType base_line_type) {
    std::cout << "======== calculate serialization rate ========\n";
    calculate_serialize_rate(map, base_line_type, SampleType::RECT);
    calculate_serialize_rate(map, base_line_type, SampleType::RECTS);
    calculate_serialize_rate(map, base_line_type, SampleType::PERSON);
    calculate_serialize_rate(map, base_line_type, SampleType::PERSONS);
    calculate_serialize_rate(map, base_line_type, SampleType::MONSTER);
    calculate_serialize_rate(map, base_line_type, SampleType::MONSTERS);
}

int main(int argc, char **argv) {
    std::cout << "OBJECT_COUNT : " << OBJECT_COUNT << std::endl;

    std::map<LibType, std::shared_ptr<base_sample>> map;
    map.emplace(LibType::STRUCT_PACK, new struct_pack_sample());

#ifdef HAVE_MSGPACK
    map.emplace(LibType::MSGPACK, new message_pack_sample());
#endif

#ifdef HAVE_BOOST_SERIALIZATION
    map.emplace(LibType::BOOST_SERIALIZATION, new boost_serialization_sample());
#endif

#ifdef HAVE_PROTOBUF
#    ifdef HAVE_STRUCT_PB
    map.emplace(LibType::STRUCT_PB, new struct_pb_sample::struct_pb_sample_t());
#    endif
    map.emplace(LibType::PROTOBUF, new protobuf_sample_t());
#endif
#ifdef HAVE_FLATBUFFER
    map.emplace(LibType::FLATBUFFER, new flatbuffer_sample_t());
#endif

    for (auto [lib_type, sample] : map) {
        std::cout << "======= bench " << sample->name() << " =======\n";
        sample->create_samples();
        sample->do_serialization();
        sample->do_deserialization();
    }

    std::cout << "======= serialize buffer size ========\n";
    for (auto [lib_type, sample] : map) {
        sample->print_buffer_size(lib_type);
    }

    run_benchmark(map, LibType::STRUCT_PACK);

#ifdef HAVE_STRUCT_PB
    run_benchmark(map, LibType::STRUCT_PB);
#endif

    return 0;
}
